package cn.thoughtworks.school.programCenter.configrations;

import cn.thoughtworks.school.programCenter.entities.User;
import cn.thoughtworks.school.programCenter.repositories.AssignmentRepository;
import cn.thoughtworks.school.programCenter.repositories.TaskRepository;
import cn.thoughtworks.school.programCenter.services.ProgramService;
import org.apache.commons.lang.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;

public class ProgramInterceptor implements HandlerInterceptor {

    @Autowired
    private ProgramService programService;
    @Autowired
    private AssignmentRepository assignmentRepository;
    @Autowired
    private TaskRepository taskRepository;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse httpServletResponse, Object o) throws Exception {

        String uri = request.getServletPath();
        if (needToInterceptUrl(uri)) {
            String token = getTokenHeader(request);
            User user = TokenUtil.parseTokenToJsonObject(token);
            if (hasPermission(user, uri)) {
                return true;
            }
            httpServletResponse.setStatus(403);
            return false;
        }

        return true;
    }

    private boolean hasPermission(User user, String uri) {
        long userId = user.getId();
        List<String> uriArray = Arrays.asList(uri.split("/"));
        return hasAdminRole(user) ||
                hasStudentOrTutorProgram(userId, uri, uriArray)||
                hasStudentProgram(userId, uri, uriArray) ||
                hasTutorProgram(userId, uri, uriArray);
    }

    private String getTokenHeader(HttpServletRequest request) {
        return request.getHeader("token");
    }

    private boolean needToInterceptUrl(String uri) {
        return tutorAndStudentUrls().stream().anyMatch(uri::matches) ||
                tutorUrls().stream().anyMatch(uri::matches) ||
                studentUrls().stream().anyMatch(uri::matches);
    }

    private boolean hasAdminRole(User user) {
        return user.getRoles().contains(9) ||
                user.getRoles().contains(8) ||
                user.getRoles().contains(7);
    }

    private boolean hasStudentOrTutorProgram(long studentId, String uri, List<String> uriArray) {
        if (tutorAndStudentUrls().stream().anyMatch(uri::matches) && getProgramId(uriArray) > 0) {
            return programService.isInStudentProgram(getProgramId(uriArray), studentId)
                    || programService.isInTutorProgram(getProgramId(uriArray), studentId);
        }
        return false;
    }

    private boolean hasTutorProgram(long studentId, String uri, List<String> uriArray) {
        if (tutorUrls().stream().anyMatch(uri::matches) && getProgramId(uriArray) > 0) {
            return programService.isInTutorProgram(getProgramId(uriArray), studentId);
        }
        return false;
    }

    private boolean hasStudentProgram(long studentId, String uri, List<String> uriArray) {
        if (studentUrls().stream().anyMatch(uri::matches) && getProgramId(uriArray) > 0) {
            return programService.isInStudentProgram(getProgramId(uriArray), studentId);
        }
        return false;
    }

    private List<String> studentUrls() {

        String tasks_assignments = "/api/v2/tasks/\\d+/assignments";
        String assignments_quizzes = "/api/v2/assignments/\\d+/quizzes/\\d+";
        String assignments_quizzes_review = "/api/v2/assignments/\\d+/quizzes/\\d+/review";
        String assignments_quizzes_suggestions = "/api/v2/assignments/\\d+/quizzes/\\d+/suggestions";
        String[] array = {tasks_assignments, assignments_quizzes, assignments_quizzes_review, assignments_quizzes_suggestions};
        return Arrays.asList(array);
    }

    private List<String> tutorUrls() {
        String programs = "/api/myStudents/programs/\\d+";
        String students = "/api/v2/myStudents/programs/\\d+/assignments/\\d+/students";
        String assignments = "/api/v2/myStudents/programs/\\d+/students/assignments";
        String students_assignments = "/api/v2/programs/\\d+/students/\\d+/assignments";

        String[] array = {programs, students, assignments, students_assignments};
        return Arrays.asList(array);
    }

    private List<String> tutorAndStudentUrls() {
        String quizzes = "/api/v2/students/\\d+/assignments/\\d+/quizzes/\\d+";
        String review = "/api/v2/students/\\d+/assignments/\\d+/quizzes/\\d+/review";
        String suggestions = "/api/v2/students/\\d+/assignments/\\d+/quizzes/\\d+/suggestions";
        String introductions = "/api/programs/\\d+/introductions";
        String tasks = "/api/v2/tasks/\\d+";
        String visibility = "/api/v2/tasks/\\d+/assignments/visibility";
        String programs_tasks = "/api/programs/\\d+/tasks";
        String[] array = {programs_tasks,introductions, tasks, visibility, quizzes, review, suggestions};
        return Arrays.asList(array);
    }

    private Long getProgramId(List<String> uriArray) {
        if (elementId(uriArray, "assignments") > 0) {
            return assignmentRepository.findProgramIdByAssignmentId(elementId(uriArray, "assignments"));
        }
        if (elementId(uriArray, "tasks") > 0) {
            return taskRepository.findProgramIdByTaskId(elementId(uriArray, "tasks"));
        }
        if (elementId(uriArray, "programs") > 0) {
            return elementId(uriArray, "programs");
        }
        return -1L;
    }

    private Long elementId(List<String> uri, String element) {
        int addressIndex = uri.indexOf(element);
        if (hasNextNumberElement(uri, addressIndex)) {
            return Long.valueOf(uri.get(addressIndex + 1));
        }
        return (long) -1;
    }

    private boolean hasNextNumberElement(List<String> uri, int addressIndex) {
        return addressIndex > 0 && addressIndex + 1 < uri.size() && NumberUtils.isNumber(uri.get(addressIndex + 1));
    }
}
